#include "tryte.h++"

Tryte::Tryte() {
	
}

Tryte::Tryte(const Tryte_t& value) {
	opSet(value);
}

Tryte::Tryte(const std::string& value) {
	for (int i = 0; i < TRITS_PER_TRYTE; ++i) {
		if (value.at(TRITS_PER_TRYTE - i - 1) == '-') {
			ternary_[i] = -1;
		} else if (value.at(TRITS_PER_TRYTE - i - 1) == '0') {
			ternary_[i] = 0;
		} else if (value.at(TRITS_PER_TRYTE - i - 1) == '+') {
			ternary_[i] = 1;
		} else {
			// ERROR
		}
	}
}

Tryte_t Tryte::getInt() const {
	Tryte_t integer = 0;
	Tryte_t multiplier = 1;
	for (int i = 0; i < TRITS_PER_TRYTE; ++i) {
		if (ternary_[i] == 1) {
			integer += multiplier;
		} else if (ternary_[i] == -1) {
			integer -= multiplier;
		}
		multiplier *= 3;
	}
	return integer;
}

std::string Tryte::getString() const {
	std::string string;
	for (int i = TRITS_PER_TRYTE - 1; i >= 0; --i) {
		if (ternary_[i] == -1) {
			string += '-';
		} else if (ternary_[i] == 0) {
			string += '0';
		} else if (ternary_[i] == 1) {
			string += '+';
		} else {
			// ERROR
			string += '?';
		}
	}
	return string;
}

void Tryte::opSet(const Tryte_t& value) {
	Tryte_t temp = value;
	for (int i = 0; i < TRITS_PER_TRYTE; ++i) {
		if ((temp + 1) % 3 == 0) {
			ternary_[i] = -1;
		} else if (temp % 3 == 0) {
			ternary_[i] = 0;
		} else {
			ternary_[i] = 1;
		}
		temp = (temp - ternary_[i]) / 3;
	}
}

void Tryte::opSet(const Tryte& value) {
	for (int i = 0; i < TRITS_PER_TRYTE; ++i) {
		ternary_[i] = value.ternary_[i];
	}
}

void Tryte::opNot() {
	for (int i = 0; i < TRITS_PER_TRYTE; ++i) {
		ternary_[i] *= -1;
	}
}

void Tryte::opAnd(const Tryte_t& arg) {
	Tryte temp(arg);
	opAnd(temp);
}

void Tryte::opAnd(const Tryte& arg) {
	for (int i = 0; i < TRITS_PER_TRYTE; ++i) {
		if (ternary_[i] > arg.ternary_[i]) {
			ternary_[i] = arg.ternary_[i];
		}
	}
}

void Tryte::opOr(const Tryte_t& arg) {
	Tryte temp(arg);
	opOr(temp);
}

void Tryte::opOr(const Tryte& arg) {
	for (int i = 0; i < TRITS_PER_TRYTE; ++i) {
		if (ternary_[i] < arg.ternary_[i]) {
			ternary_[i] = arg.ternary_[i];
		}
	}
}

void Tryte::opXor(const Tryte_t& arg) {
	Tryte temp(arg);
	opXor(temp);
}

void Tryte::opXor(const Tryte& arg) {
	for (int i = 0; i < TRITS_PER_TRYTE; ++i) {
		if (arg.ternary_[i] == 1) {
			ternary_[i] *= -1;
		} else if (arg.ternary_[i] == 0) {
			ternary_[i] = 0;
		}
	}
}

Trit_t Tryte::opShl(const Tryte_t& arg, const Trit_t& carry) {
	if (arg < 0) {
		opShr(-1 * arg, carry);
		return 0;
	} else if (arg == 0) {
		return 0;
	}
	Trit_t overflow = ternary_[TRITS_PER_TRYTE - arg];
	for (int i = TRITS_PER_TRYTE - 1; i >= arg; --i) {
		ternary_[i] = ternary_[i - arg];
	}
	ternary_[arg - 1] = carry;
	for (int i = arg - 2; i >= 0; --i) {
		ternary_[i] = 0;
	}
	return overflow;
}

Trit_t Tryte::opShl(const Tryte& arg, const Trit_t& carry) {
	return opShl(arg.getInt(), carry);
}

Trit_t Tryte::opShr(const Tryte_t& arg, const Trit_t& carry) {
	if (arg < 0) {
		opShl(-1 * arg, carry);
		return 0;
	} else if (arg == 0) {
		return 0;
	}
	Trit_t overflow = ternary_[arg - 1];
	for (int i = 0; i < TRITS_PER_TRYTE - arg; ++i) {
		ternary_[i] = ternary_[i + arg];
	}
	ternary_[TRITS_PER_TRYTE - arg] = carry;
	for (int i = TRITS_PER_TRYTE - arg + 1; i < TRITS_PER_TRYTE; ++i) {
		ternary_[i] = 0;
	}
	return overflow;
}

Trit_t Tryte::opShr(const Tryte& arg, const Trit_t& carry) {
	return opShr(arg.getInt(), carry);
}

void Tryte::opInc() {
	for (int i = 0; i < TRITS_PER_TRYTE; ++i) {
		if (ternary_[i] == 1) {
			ternary_[i] = -1;
		} else {
			++ternary_[i];
			return;
		}
	}
}

void Tryte::opInc3() {
	for (int i = 1; i < TRITS_PER_TRYTE; ++i) {
		if (ternary_[i] == 1) {
			ternary_[i] = -1;
		} else {
			++ternary_[i];
			return;
		}
	}
}

void Tryte::opDec() {
	for (int i = 0; i < TRITS_PER_TRYTE; ++i) {
		if (ternary_[i] == -1) {
			ternary_[i] = 1;
		} else {
			--ternary_[i];
			return;
		}
	}
}

void Tryte::opDec3() {
	for (int i = 1; i < TRITS_PER_TRYTE; ++i) {
		if (ternary_[i] == -1) {
			ternary_[i] = 1;
		} else {
			--ternary_[i];
			return;
		}
	}
}

Trit_t Tryte::opAdd(const Tryte_t& arg, const Trit_t& carry) {
	Tryte temp(arg);
	return opAdd(temp, carry);
}

Trit_t Tryte::opAdd(const Tryte& arg, const Trit_t& carry) {
	Trit_t temp = carry;
	for (int i = 0; i < TRITS_PER_TRYTE; ++i) {
		switch (ternary_[i] + arg.ternary_[i] + temp) {
			case -3:
				temp = -1;
				ternary_[i] = 0;
				break;
			case -2:
				temp = -1;
				ternary_[i] = 1;
				break;
			case -1:
				temp = 0;
				ternary_[i] = -1;
				break;
			case 0:
				temp = 0;
				ternary_[i] = 0;
				break;
			case 1:
				temp = 0;
				ternary_[i] = 1;
				break;
			case 2:
				temp = 1;
				ternary_[i] = -1;
				break;
			case 3:
				temp = 1;
				ternary_[i] = 0;
				break;
		}
	}
	return temp;
}

Trit_t Tryte::opSub(const Tryte_t& arg, const Trit_t& carry) {
	Tryte temp(arg);
	return opSub(temp, carry);
}

Trit_t Tryte::opSub(const Tryte& arg, const Trit_t& carry) {
	Trit_t temp = carry;
	for (int i = 0; i < TRITS_PER_TRYTE; ++i) {
		switch (ternary_[i] - arg.ternary_[i] + temp) {
			case -3:
				temp = -1;
				ternary_[i] = 0;
				break;
			case -2:
				temp = -1;
				ternary_[i] = 1;
				break;
			case -1:
				temp = 0;
				ternary_[i] = -1;
				break;
			case 0:
				temp = 0;
				ternary_[i] = 0;
				break;
			case 1:
				temp = 0;
				ternary_[i] = 1;
				break;
			case 2:
				temp = 1;
				ternary_[i] = -1;
				break;
			case 3:
				temp = 1;
				ternary_[i] = 0;
				break;
		}
	}
	return temp;
}

Trit_t Tryte::getTrit(const size_t& tritNo) {
	return ternary_[tritNo];
}

void Tryte::setTrit(const size_t& tritNo, const Trit_t& value) {
	ternary_[tritNo] = value;
}

